/* eslint-disable @typescript-eslint/indent */
import dragula from '@farris/designer-dragula';
import { DesignerHTMLElement, DraggingResolveContext, UseDragula } from '../types';
import { findIndex } from 'lodash';
import { ref } from 'vue';
import { cavasChanged } from '../designer-cavas-changed';
import { ComponentSchema } from '../../types';

export function useDragula(): UseDragula {

    let dragulaInstance: any;

    /**
     * 获取拖拽上下文信息
     * @param sourceEl 拖拽源HTML节点
     * @param targetComponentInstance 目标组件实例
     */
    function getComponentResolveContext(
        sourceElement: DesignerHTMLElement,
        targetContainer: DesignerHTMLElement,
        sourceContainer: DesignerHTMLElement
    ): DraggingResolveContext {

        const resolveContext: DraggingResolveContext = {};

        resolveContext.sourceElement = sourceElement;
        resolveContext.sourceContainer = sourceContainer;
        resolveContext.targetContainer = targetContainer;

        resolveContext.sourceType = sourceElement.getAttribute('data-sourceType') || 'move' as any;
        resolveContext.controlType = sourceElement.getAttribute('data-controlType');
        resolveContext.controlTypeName = sourceElement.getAttribute('data-controlTypeName');
        resolveContext.controlCategory = sourceElement.getAttribute('data-category');
        resolveContext.controlFeature = sourceElement.getAttribute('data-feature');
        resolveContext.parentComponentInstance = targetContainer.componentInstance;

        if (resolveContext.controlCategory === 'input') {
            resolveContext.bindingType = 'field';
        } else if (resolveContext.controlCategory === 'dataCollection') {
            resolveContext.bindingType = 'entity';
        }

        // 现有控件移动位置：从控件实例上获取控件类型
        if (sourceElement.componentInstance) {
            resolveContext.controlType = sourceElement.componentInstance.value.schema.type;
            resolveContext.controlCategory = sourceElement.componentInstance.value.schema.category;

        }
        return resolveContext;
    }

    /**
     * 判断是否可以接收拖拽的新控件
     * @param el 拖拽的新控件元素
     * @param target  目标位置
     * @returns boolean
     */
    function checkCanAcceptDrops(
        element: DesignerHTMLElement,
        target: DesignerHTMLElement,
        sourceContainer: DesignerHTMLElement
    ): boolean {
        if (!!element.contains(target) || target.classList.contains('no-drop')) {
            return false;
        }
        const result = true;
        if (element.componentInstance && element.componentInstance.value.getDragScopeElement) {
            const dragScopEle = element.componentInstance.value.getDragScopeElement();
            if (dragScopEle) {
                if (!dragScopEle.contains(target)) {
                    return false;
                }
            }
        }
        if (target.componentInstance && target.componentInstance.value.canAccepts) {
            const draggingContext = getComponentResolveContext(element, target, sourceContainer);

            return target.componentInstance.value.canAccepts(draggingContext);
        }
        return result;
    }

    /**
     * 判断DOM 是否在可视区域内
     * @param el 元素
     * @param containerEl 容器
     */
    function isElementInViewport(element: HTMLElement, sourceContainer: HTMLElement) {
        const container = sourceContainer.getBoundingClientRect();
        const box = element.getBoundingClientRect();
        const top = box.top >= container.top;
        const bottom = box.top < container.bottom;
        return (top && bottom);
    }

    /**
     * 拖拽过程若中产生了页面的上下滚动，需要将已选控件的操作按钮上下移动相等的距离。
     * @param formElement 滚动父容器
     * @param scrollDirection 滚动方向
     * @param scrollHeight 滚动距离
     */
    function scrollInDragging(formElement: HTMLElement, scrollHeight: number) {
        const selectedDom = formElement.querySelector('.dgComponentSelected') as HTMLElement;
        if (!selectedDom || scrollHeight === 0) {
            return;
        }
        if (isElementInViewport(selectedDom, formElement)) {
            const toolbar = selectedDom.querySelector('.component-btn-group');
            if (toolbar) {
                const divPanel = toolbar.querySelector('div');
                if (divPanel && divPanel.style.top) {
                    const top = Number.parseFloat(divPanel.style.top);
                    divPanel.style.top = (top - scrollHeight) + 'px';
                }
            }
        }
    }

    /**
     * 将新控件json添加到新容器schema json中
     * @param target 目标容器元素
     * @param sourceControlSchema 新控件的JSON schema结构
     * @param sibling 目标位置的下一个同级元素
     */
    function addNewControlToTarget(
        target: DesignerHTMLElement,
        sourceControlSchema: ComponentSchema | null,
        sibling: DesignerHTMLElement
    ): number {
        const parent = target.componentInstance;
        let index = -1;
        if (!sourceControlSchema) {
            return -1;
        }
        if (target.componentInstance.value.contents) {
            if (sibling && sibling.componentInstance) {
                if (!sibling.getAttribute('data-noattach')) {
                    // 定位目标位置
                    const siblingComponentSchema = sibling.componentInstance.value.schema;
                    let locatePredicate: any = { id: siblingComponentSchema.id };
                    if (siblingComponentSchema.type === 'Component') {
                        locatePredicate = { component: siblingComponentSchema.id };
                    }

                    index = findIndex(target.componentInstance.value.contents, locatePredicate);
                    index = (index === -1) ? 0 : index;
                } else {
                    index = Number(sibling.getAttribute('data-position'));
                }
                if (index !== -1) {
                    target.componentInstance.value.contents.splice(index, 0, sourceControlSchema);
                }
            } else {
                target.componentInstance.value.contents.push(sourceControlSchema);
            }
        }
        return index;
    }

    /**
     * 获取新控件的目标位置
     */
    function getNewControlTargetPosition(target: DesignerHTMLElement, sibling: DesignerHTMLElement): number {

        // 不允许放置
        if (!target.componentInstance.value.contents) {
            return -1;
        }

        // 空容器：放置第1个位置
        if (target.componentInstance.value.contents.length === 0) {
            return 0;
        }

        // 后面没有兄弟控件：放置到最后
        if (!sibling || !sibling.componentInstance) {
            return target.componentInstance.value.contents.length;
        }

        // noattach???
        if (sibling.getAttribute('data-noattach')) {
            return Number(sibling.getAttribute('data-position'));
        }

        const siblingComponentSchema = sibling.componentInstance.value.schema;
        let locatePredicate;
        if (siblingComponentSchema.type === 'Component') {
            locatePredicate = { component: siblingComponentSchema.id };
        } else {
            locatePredicate = { id: siblingComponentSchema.id };
        }
        let position = findIndex(target.componentInstance.value.contents, locatePredicate);
        position = (position === -1) ? 0 : position;

        return position;
    }

    /**
     * 从控件工具箱中拖拽新建控件
     * @param element 拖拽的元素
     * @param target 目标容器元素
     * @param source 原容器元素
     * @param sibling 目标位置的下一个同级元素
     */
    function createControlFromOutside(
        element: DesignerHTMLElement,
        target: DesignerHTMLElement,
        source: HTMLElement,
        sibling: DesignerHTMLElement
    ) {
        if (target.componentInstance.value.onAcceptNewChildElement) {
            const targetPosition = getNewControlTargetPosition(target, sibling);
            const newComponentSchema = target.componentInstance.value.onAcceptNewChildElement(element, targetPosition);
            addNewControlToTarget(target, newComponentSchema, sibling);
        }
        // 移除拷贝生成的源DOM
        if (target.contains(element)) {
            target.removeChild(element);
        }
    }

    /**
     * 在现有的表单中拖拽移动控件位置
     * @param element 拖拽的元素
     * @param target 目标容器元素
     * @param source 源容器元素
     * @param sibling 目标位置的下一个同级元素
     */
    function dragBetweenCurrentForm(
        element: DesignerHTMLElement,
        target: DesignerHTMLElement,
        source: DesignerHTMLElement,
        sibling: DesignerHTMLElement
    ) {
        let sourceControlSchema;
        let index = -1;
        // Form、DataGrid等控件在拖拽时，需要连同所属Component一起拖拽。
        if (element.componentInstance && element.componentInstance.value.triggerBelongedComponentToMoveWhenMoved) {
            const cmpInstance = element.componentInstance.value.getBelongedComponentInstance(element.componentInstance);
            if (cmpInstance) {
                // 将拖拽元素替换为所属Component
                element = ref(cmpInstance.elementRef).value.parentElement as DesignerHTMLElement;
                // 将源容器元素替换为所属Component的父级元素
                source = element.parentElement as DesignerHTMLElement;
            }

        }
        const elementComponentSchema = element.componentInstance && element.componentInstance.value.schema;

        const locatePredicate: any = { id: elementComponentSchema && elementComponentSchema.id };
        index = findIndex(source.componentInstance.value.contents, locatePredicate);

        if (index !== -1 && source.componentInstance.value.contents) {
            // 从源容器schema json中移除
            sourceControlSchema = source.componentInstance.value.contents.splice(index, 1);

            sourceControlSchema = sourceControlSchema[0];
        }

        addNewControlToTarget(target, sourceControlSchema as ComponentSchema, sibling);

        // 源容器的控件被移除掉
        if (source.componentInstance && source.componentInstance.value.onChildElementMovedOut) {
            source.componentInstance.value.onChildElementMovedOut(element);
        }

        // 目标容器接收新控件
        if (target.componentInstance && target.componentInstance.value.onAcceptMovedChildElement) {
            target.componentInstance.value.onAcceptMovedChildElement(element, source);
        }
    }

    /**
     * 拖拽结束
     * @param element 拖拽的元素
     * @param target 目标容器元素
     * @param source 原容器元素
     * @param sibling 目标位置的下一个同级元素
     */
    function onDrop(element: DesignerHTMLElement, target: DesignerHTMLElement, source: DesignerHTMLElement, sibling: DesignerHTMLElement) {
        if (!target) {
            return;
        }
        // If you try to drop within itself.
        if (element.contains(target)) {
            return;
        }
        const sourceType = element.getAttribute('data-sourceType');

        switch (sourceType) {
            case 'control': case 'field': case 'entity': {
                createControlFromOutside(element, target, source, sibling);
                break;
            }
            default: {
                if (source.componentInstance.value.contents) {
                    dragBetweenCurrentForm(element, target, source, sibling);
                } else {
                    // 移除拷贝生成的源DOM
                    // eslint-disable-next-line no-lonely-if
                    if (target.contains(element)) {
                        target.removeChild(element);
                    }
                }
            }
        }
        cavasChanged.value++;

    }

    function initializeDragula(containerElement: DesignerHTMLElement) {
        if (dragulaInstance) {
            dragulaInstance.destroy();
        }

        if (!dragula) {
            return;
        }

        dragulaInstance = dragula([], {
            // 镜像容器
            mirrorContainer: containerElement,
            direction: 'mixed',
            revertOnSpill: true,
            // 判断是否可移动
            moves(element: DesignerHTMLElement, container: DesignerHTMLElement, handle: DesignerHTMLElement): boolean {
                let moves = true;

                // 包含no-drag样式的元素不允许拖动
                if (element.classList.contains('no-drag')) {
                    moves = false;
                }
                // 为防止误操作，可视化区域的控件只能通过移动图标来拖拽
                if (element.componentInstance) {
                    moves = handle.classList.contains('f-icon-yxs_move') && !!handle.getAttribute('data-dragging-icon');
                }
                return moves;
            },
            // 判断是否可拷贝
            copy(element: HTMLElement): boolean {
                // 工具箱里的div需要配置drag-copy
                return element.classList.contains('drag-copy');
            },
            // 获取镜像元素的文本内容
            getMirrorText(element: DesignerHTMLElement): string {
                if (element.componentInstance && element.componentInstance.value.getDraggingDisplayText) {
                    return element.componentInstance.value.getDraggingDisplayText();
                }
                return element.innerText || '控件';
            },
            // 判断目标区域是否可接收拖拽的控件
            accepts(element: DesignerHTMLElement, target: DesignerHTMLElement, source: DesignerHTMLElement): boolean {
                const canAccept = checkCanAcceptDrops(element, target, source);
                const guMirrotElement = containerElement.lastElementChild as Element;
                if (canAccept) {
                    guMirrotElement.className = guMirrotElement.className.replace('undroppable', '');
                } else if (!guMirrotElement.className.includes('undroppable')) {
                    guMirrotElement.className += ' undroppable';
                }
                return canAccept;
            }
        }).on('over', (el: DesignerHTMLElement, container: DesignerHTMLElement) => {
            container.className += ' drag-over';
        }).on('out', (el: DesignerHTMLElement, container: DesignerHTMLElement) => {
            container.className = container.className.replace('drag-over', '').replace('  ', '');
        }).on('drop', (
            element: DesignerHTMLElement, target: DesignerHTMLElement, source: DesignerHTMLElement, sibling: DesignerHTMLElement
        ) => {
            onDrop(element, target, source, sibling);
        }).on('dragend', (element: HTMLElement, scrollHeight: number) => {
            scrollInDragging(element, scrollHeight);
        });
    }

    /**
     * 子组件JSON结构和当前组件的实例添加到DOM中并注册拖拽容器。节点class= 'builder-components...'
     * @param element dom元素
     * @param childrenComponents 容器内的子组件实例集合
     * @param childrenContents 子组件JSON schema集合
     * @param component 容器组件实例
     * @returns 容器类组件的子组件集合
     */
    function attachComponents(element: HTMLElement, component: Record<string, any>) {

        // don't attach if no element was found or component doesn't participate in drag'n'drop.
        if (!element) {
            return;
        }
        if (component.noDragDrop) {
            return element;
        }
        // 获取容器中的子组件集合节点
        const containerElement: HTMLElement = element.querySelector(`[data-dragref='${component.id}-container']`) || element;

        // 将容器添加到拖拽列表中，dragula控件会监听容器中元素的拖动事件
        if (dragulaInstance && containerElement) {
            // containerElement 为页面中的容器节点的builder-components层级
            dragulaInstance.containers.push(containerElement);
        }
    }

    /**
     * 将工具箱各容器添加到dragula的拖拽列表中
     */
    function attachToolbox() {
        if (!dragulaInstance) {
            return;
        }
        const controlPanels = document.getElementsByClassName('controlCategory');
        if (!controlPanels) {
            return;
        }

        dragulaInstance.containers = dragulaInstance.containers.filter(
            (element: HTMLElement) => !element.className.includes('controlCategory')
        );

        Array.from(controlPanels).forEach((panelElement) => {
            dragulaInstance.containers.push(panelElement);
        });

    }

    return { attachComponents, attachToolbox, initializeDragula };

}
